package org.flashamaphone.controller
{
	import flash.events.TimerEvent;
	import flash.media.Sound;
	import flash.utils.ByteArray;
	import flash.utils.Timer;
	import flash.utils.getQualifiedSuperclassName;
	import flash.utils.getTimer;
	
	import org.flashamaphone.buffer.LiveBuffer;
	import org.flashamaphone.debug.Debugger;
	import org.flashamaphone.tones.ITone;
	import org.flashamaphone.tones.Note;
	import org.flashamaphone.tones.Tone;

	/**
	 * Keyboard Controller Board allows interaction with notes like a keyboard
	 * Notes can be pressed and released
	 */
	public class KeyboardControllerBoard implements ISoundController
	{	
		/** keys pressed */
		protected var keysPressed:Vector.<ITone> = new Vector.<ITone>();
		
		/** keys released still in release phase */
		protected var keysReleased:Vector.<ITone> = new Vector.<ITone>();
		
		/** sound buffer */
		protected var _soundbuffer:LiveBuffer;
		
		/** debugger */
		protected var _debug:Boolean = false;

		/**
		 * sound buffer setter
		 * 
		 * @param value
		 */
		public function set soundBuffer(value:LiveBuffer):void {
			_soundbuffer = value;
		}
		
		/**
		 * debug getter
		 * 
		 * @return debug
		 */
		public function get debug():Boolean {
			return _debug;
		}
		
		/**
		 * debug setter
		 * 
		 * @param value
		 */
		public function set debug(value:Boolean):void {
			_debug = value;
		}
		
		
		/** constructor */
		public function KeyboardControllerBoard(){}
		
		
		/**
		 * on key press
		 * 
		 * @param note
		 */
		public function pressKey(note:ITone):void {
			note.resetByteStream();
			keysPressed.push(note);
			if (!_soundbuffer.active) {
				_soundbuffer.activateBuffer(this);
			}
		}
		
		/**
		 * on key release
		 * 
		 * @param note
		 */
		public function releaseKey(note:ITone):void {
			var indx:int = keysPressed.indexOf(note);
			if (indx != -1) {
				keysPressed.splice(indx, 1);
				note.phase = Tone.RELEASE_PHASE;
				keysReleased.push(note);
			}
		}
		
		/**
		 * pull bytes from pressed notes
		 * 
		 * @param number of stereo samples to pull
		 * @return bytes pulled
		 */
		public function pull(numStereoSamples:int):ByteArray {
			var keys:Vector.<ITone> = new Vector.<ITone>();
			var key:ITone;
			
			// accumulate all keys pressed....
			for each (key in keysPressed) {
				keys.push(key);	
			}
			// ... with all keys released
			for each (key in keysReleased) {
				keys.push(key);	
			}
			
			// mix the signals
			var returnBytes:ByteArray = mix(keys, numStereoSamples);
			if (debug) {
				Debugger.debug(returnBytes);
			}
			
			return returnBytes;			
		}
		
		/**
		 * mix all keys pressed
		 * 
		 * @param keys (ITone vector)
		 * @param number of samples
		 * @return mixed bytearray
		 */
		protected function mix(keys:Vector.<ITone>, numSamples:int):ByteArray {
			var numBytes:int = numSamples*8;
			var bytesList:Vector.<ByteArray> = new Vector.<ByteArray>();
			
			// get bytes for all keys for the number of samples desired
			for each (var key:ITone in keys) {
				var sound:ByteArray = key.stream(numBytes);
				
				// remove key if no bytes are left - ie release is over
				if (sound.length < numBytes) {
					keysReleased.splice( keysReleased.indexOf(key), 1);
				}
				
				sound.position = 0;
				bytesList.push(sound);
			}
			
			// break out each sample, add them up, and re-write byte array
			var mix:ByteArray = new ByteArray();
			for (var c:int = 0; c < numSamples; c++) {
				var combinedSample:Number = 0;
				var samplesCounted:int = 0;
				for each (var bytes:ByteArray in bytesList) {
					if (bytes.bytesAvailable >= 8) {
						var left:Number = bytes.readFloat(); // left
						var right:Number = bytes.readFloat(); // right
						combinedSample += (left + right)/2;
						samplesCounted ++;
					}
				}
				
				if (samplesCounted > 0) {
					mix.writeFloat(combinedSample/samplesCounted); // left
 					mix.writeFloat(combinedSample/samplesCounted); // right
				}
			}
			return mix;
		}
	}
}